package outbound_task

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"git.mycaigou.com/gfyx/common/tenant_db"
	"git.mycaigou.com/gfyx/common/usermetadata"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/constants"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/errcode"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/msg_center"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/ossfile"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/utils"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/dto/market"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/base/user"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/common/file"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/market/outbound_crowd"
	outboundRccProject "git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/market/outbound_rcc_project"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/market/outbound_task"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/remote/baiying_remote"
	bigdataRemote "git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/remote/bigdata_remote"
	basePb "git.mycaigou.com/gfyx/micro-gfyx-oppty-service/proto/micro_gfyx_oppty_service_proto/micro-gfyx-oppty-service/base"
	cluePb "git.mycaigou.com/gfyx/micro-gfyx-oppty-service/proto/micro_gfyx_oppty_service_proto/micro-gfyx-oppty-service/clue"
	pb "git.mycaigou.com/gfyx/micro-gfyx-oppty-service/proto/micro_gfyx_oppty_service_proto/micro-gfyx-oppty-service/market"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/service/base/msg"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/service/clue/clue"
	"git.myscrm.cn/golang/common/ykerrcode"
	"git.myscrm.cn/golang/stark/v4"
	"github.com/xuri/excelize/v2"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type outboundTaskService struct {
	baiyingRepo            baiying_remote.BaiYingRepositoryIface
	fileRepo               file.FileRepoIface
	outboundTaskRepo       outbound_task.OutBoundTaskRepositoryIface
	outboundCrowdRepo      outbound_crowd.OutboundCrowdReposIface
	userRepo               user.UserRepositoryIface
	userBindRepo           user.GfyxUserBindReposIface
	bigDataServiceRepo     bigdataRemote.BigDataServiceRepositoryIface
	clueService            clue.ClueServiceIface
	msgService             msg.MsgServiceIface
	outboundRccProjectRepo outboundRccProject.OutBoundRccProjectRepositoryIface
}

func NewOutboundTaskService(
	baiyingRepo baiying_remote.BaiYingRepositoryIface,
	fileRepo file.FileRepoIface,
	outboundTaskRepo outbound_task.OutBoundTaskRepositoryIface,
	outboundCrowdRepo outbound_crowd.OutboundCrowdReposIface,
	userRepo user.UserRepositoryIface,
	userBindRepo user.GfyxUserBindReposIface,
	bigDataServiceRepo bigdataRemote.BigDataServiceRepositoryIface,
	clueService clue.ClueServiceIface,
	msgService msg.MsgServiceIface,
	outboundRccProjectRepo outboundRccProject.OutBoundRccProjectRepositoryIface,
) OutboundTaskServiceIface {
	return &outboundTaskService{
		baiyingRepo:            baiyingRepo,
		fileRepo:               fileRepo,
		outboundTaskRepo:       outboundTaskRepo,
		outboundCrowdRepo:      outboundCrowdRepo,
		userRepo:               userRepo,
		userBindRepo:           userBindRepo,
		bigDataServiceRepo:     bigDataServiceRepo,
		clueService:            clueService,
		msgService:             msgService,
		outboundRccProjectRepo: outboundRccProjectRepo,
	}
}

func (s *outboundTaskService) ImportCrowd(ctx context.Context, request *pb.ImportCrowdRequest) (*pb.ImportCrowdResponse, error) {
	if request.CrowdId != 0 {
		err := s.importCustomerFromCrowd(ctx, int32(request.CrowdId), request.TaskId, request.CompanyId)
		if err != nil {
			stark.Logger.Errorf(ctx, "ImportCrowd importCustomerFromCrowd request:%+v, err:%s", request, err.Error())
			return nil, err
		}
	}

	if request.FileId != 0 {
		// 从excel文件导入
		err := s.importCustomerFromExcel(ctx, request.TaskId, request.FileId, request.CompanyId)
		if err != nil {
			stark.Logger.Errorf(ctx, "ImportCrowd importCustomerFromExcel request:%+v, err:%s", request, err.Error())
			return nil, err
		}
	}
	return &pb.ImportCrowdResponse{IsSucceed: true}, nil
}

func (s *outboundTaskService) importCustomerFromCrowd(ctx context.Context, crowdId int32, taskId, companyId int64) error {
	// 从人群包导入，获取人群包的oss文件，解析文件内容，调用百应接口批量导入
	crowdPo, err := s.outboundCrowdRepo.GetOutboundCrowd(ctx, crowdId)
	if err != nil {
		return err
	}
	if crowdPo == nil || crowdPo.Id == 0 {
		return errors.New("人群包不存在")
	}
	if crowdPo.Status == constants.CrowdStatusRunning {
		return errors.New("人群包正在生成中，请稍后再试")
	}
	newCtx, err := tenant_db.GetTenantBackgroundContext(ctx, "")
	if err != nil {
		return err
	}
	if crowdPo.Type == constants.CrowdTypeRpa {
		// RPA的人群包
		go func(ctx context.Context) {
			pageInfo := utils.PageInfo{
				PageSize: 2000,
				Page:     1,
				Total:    0,
			}
			for {
				//轮询获取rpa联系人
				rpaCrowdData, err := s.bigDataServiceRepo.GetCrowdByRpa(ctx, &bigdataRemote.GetCrowdByRpaRequest{
					PageNum:  int32(pageInfo.Page),
					PageSize: int32(pageInfo.PageSize),
				})
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd GetCrowdByRpa crowdId:%d, pageInfo:%+v, err:%s", crowdId, pageInfo, err.Error())
					return
				}
				importRequest := &baiying_remote.CustomerImportRequest{
					CallJobId:          taskId,
					CompanyId:          companyId, // 从缓存中获取公司id
					CustomerInfoVOList: nil,
				}
				if rpaCrowdData == nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd GetCrowdByRpa crowdId:%d, pageInfo:%+v, rpaCrowdData is nil", crowdId, pageInfo)
					return
				}
				pageInfo.Total = int64(rpaCrowdData.Total)
				for _, item := range rpaCrowdData.List {
					name := ""
					// 如果item.CompanyName和 item.PersonName都不为空则用-将两个值拼接，否则则用不为空的那个值给name字段赋值
					if item.CompanyName != "" && item.PersonName != "" {
						name = fmt.Sprintf("%s-%s", item.CompanyName, item.PersonName)
					} else if item.CompanyName != "" {
						name = item.CompanyName
					} else if item.PersonName != "" {
						name = item.PersonName
					}

					//rcc的用户姓名后面都多了一个空格，去掉该空格
					name = strings.TrimSpace(name)

					importRequest.CustomerInfoVOList = append(importRequest.CustomerInfoVOList, &baiying_remote.CustomerItem{
						Phone: item.PersonPhone,
						Name:  name,
					})

					//保存rcc的项目名称和用户手机号的关系
					_, err = s.outboundRccProjectRepo.Save(ctx, &outboundRccProject.GfyxOutboundRccProjectPo{
						Id:             0,
						TaskId:         taskId,
						Mobile:         item.PersonPhone,
						CustomerName:   name,
						RccProjectName: item.RccProjectName,
						IsDeleted:      0,
						CreatedOn:      time.Now(),
						CreatedBy:      "",
						ModifiedOn:     time.Now(),
						ModifiedBy:     "",
					})
					if err != nil {
						stark.Logger.Errorf(ctx, "importCustomerFromCrowd Save rccProjectName:%s, mobile:%s, err:%s", item.RccProjectName, item.PersonPhone, err.Error())
					}
				}
				_, err = s.baiyingRepo.CustomerImport(ctx, importRequest.CompanyId, importRequest)
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd CustomerImport importRequest:%+v, err:%s", importRequest, err)
					return
				}

				//大于10万是兜底策略，避免无限死循环，真实的数据并没有那么多
				if pageInfo.Page > 100000 || int64(pageInfo.Page)*int64(pageInfo.PageSize) > pageInfo.Total {
					break
				}

				pageInfo.Page++
			}

		}(newCtx)
	} else {
		// 甲方单位、中标单位的人群包
		go func(ctx context.Context) {
			crowdFileList, err := s.outboundCrowdRepo.GetOutboundCrowdFileByCondition(ctx,
				map[string]interface{}{"crowd_id": crowdId, "crowd_version": crowdPo.Version,
					"is_preview_data": 0,
					"is_deleted":      constants.IsDeletedFalse})
			if err != nil {
				stark.Logger.Errorf(ctx, "importCustomerFromCrowd GetOutboundCrowdFileByCrowdId crowdId:%d, err:%s", crowdId, err)
				return
			}
			var fileIds []int64
			for _, crowdFile := range crowdFileList {
				fileIds = append(fileIds, crowdFile.FileId)
			}

			fileList, err := s.fileRepo.QueryByIds(ctx, fileIds)
			if err != nil {
				stark.Logger.Errorf(ctx, "importCustomerFromCrowd QueryByIds fileIds:%+v, err:%s", fileIds, err)
				return
			}
			for _, fileInfo := range fileList {
				fileUrl, err := ossfile.GetSignURL(fileInfo.FilePath)
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd GetSignURL fileInfo:%+v, err:%s", fileInfo, err)
					continue
				}
				bytesData, err := readRemoteOssData(fileUrl)
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd readRemoteOssData fileInfo:%+v, err:%s", fileInfo, err)
					continue
				}
				importRequest := &baiying_remote.CustomerImportRequest{
					CallJobId:          taskId,
					CompanyId:          companyId, // 从缓存中获取公司id
					CustomerInfoVOList: nil,
				}
				err = json.Unmarshal(bytesData, &importRequest.CustomerInfoVOList)
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd readRemoteOssData fileInfo:%+v, err:%s", fileInfo, err)
					continue
				}
				_, err = s.baiyingRepo.CustomerImport(ctx, importRequest.CompanyId, importRequest)
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromCrowd CustomerImport importRequest:%+v, err:%s", importRequest, err)
					return
				}
			}

		}(newCtx)
	}

	return nil
}

func (s *outboundTaskService) importCustomerFromExcel(ctx context.Context, taskId int64, fileId, companyId int64) error {
	// 1.从oss下载文件
	fileInfo, err := s.fileRepo.QueryById(ctx, fileId)
	if err != nil {
		return err
	}
	// 从oss下载文件
	fileUrl, err := ossfile.GetSignURL(fileInfo.FilePath)
	if err != nil {
		return err
	}
	//2.读取文件内容
	rowData, err := readRemoteExcelData(fileUrl)
	if err != nil {
		stark.Logger.Errorf(ctx, "importCustomerFromExcel：文件下载地址：%s，错误信息：%s", fileUrl, err.Error())
		return errors.New("导入文件格式错误")
	}
	if len(rowData) <= 1 {
		return errors.New("导入文件内容为空")
	} else if len(rowData) > 200001 {
		return errors.New("导入记录超过20000条,请分批导入")
	}

	newCtx, err := tenant_db.GetTenantBackgroundContext(ctx, "")
	if err != nil {
		return err
	}
	go func(ctx context.Context) {
		// 4.调用百应接口批量导入
		importRequest := &baiying_remote.CustomerImportRequest{
			CallJobId:          taskId,
			CompanyId:          companyId, // 从缓存中获取公司id
			CustomerInfoVOList: nil,
		}
		for i, row := range rowData {
			if i < 2 {
				continue
			}
			importRequest.CustomerInfoVOList = append(importRequest.CustomerInfoVOList, &baiying_remote.CustomerItem{
				Phone: row[1],
				Name:  row[0],
			})

			if i%10000 == 0 {
				_, err = s.baiyingRepo.CustomerImport(ctx, importRequest.CompanyId, importRequest)
				if err != nil {
					stark.Logger.Errorf(ctx, "importCustomerFromExcel CustomerImport importRequest:%+v, err:%s", importRequest, err)
					return
				}
				importRequest.CustomerInfoVOList = []*baiying_remote.CustomerItem{}
			}
		}
		if len(importRequest.CustomerInfoVOList) != 0 {
			_, err = s.baiyingRepo.CustomerImport(ctx, importRequest.CompanyId, importRequest)
			if err != nil {
				stark.Logger.Errorf(ctx, "importCustomerFromExcel CustomerImport importRequest:%+v, err:%s", importRequest, err)
				return
			}
		}
	}(newCtx)

	return nil
}

func readRemoteOssData(fileDownloadUrl string) ([]byte, error) {
	if fileDownloadUrl == "" {
		return nil, nil
	}

	resp, err := http.Get(fileDownloadUrl)
	if err != nil {
		return nil, err
	}

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err = resp.Body.Close(); err != nil {
			fmt.Println(err)
		}
	}()

	return data, nil
}

func readRemoteExcelData(fileDownloadUrl string) ([][]string, error) {
	var rowData [][]string
	if fileDownloadUrl == "" {
		return rowData, nil
	}

	resp, err := http.Get(fileDownloadUrl)
	if err != nil {
		return rowData, err
	}

	f, err := excelize.OpenReader(resp.Body)
	if err != nil {
		return rowData, err
	}
	defer func() {
		if err = f.Close(); err != nil {
			fmt.Println(err)
		}
		if err = resp.Body.Close(); err != nil {
			fmt.Println(err)
		}
	}()

	rowData, err = f.GetRows("Sheet1")
	if err != nil {
		return rowData, err
	}

	return rowData, nil
}

func (s *outboundTaskService) SetTaskLogRead(ctx context.Context, callInstanceId int64) error {
	conditions := map[string]interface{}{
		"call_instance_id": callInstanceId,
	}
	columns := map[string]interface{}{
		"is_read": 1,
	}
	db := (&tenant_db.TenantDB{}).GetTenantDB(ctx)
	_, err := s.outboundTaskRepo.UpdateTaskLog(ctx, db, conditions, columns)
	if err != nil {
		return err
	}
	return nil
}

func (s *outboundTaskService) UpdateIntention(ctx context.Context, request *pb.UpdateIntentionRequest) error {
	gfyxOutboundTaskLogPo, err := s.outboundTaskRepo.GetTaskLogByCallInstanceId(ctx, request.CallInstanceId)
	if err != nil {
		return err
	}
	if gfyxOutboundTaskLogPo.CallInstanceId <= 0 {
		return ykerrcode.TogRPCError(errcode.OutboundTaskUpdateIntentionError, "修改客户意向失败，通话记录不存在")
	}
	conditions := map[string]interface{}{
		"call_instance_id": request.CallInstanceId,
	}
	columns := map[string]interface{}{
		"intention": request.Intention,
	}
	tx := (&tenant_db.TenantDB{}).GetTenantDB(ctx)
	tx = tx.Begin()
	_, err = s.outboundTaskRepo.UpdateTaskLog(ctx, tx, conditions, columns)
	if err != nil {
		tx.Rollback()
		return err
	}

	userInfo := usermetadata.GetMetaUserInfo(ctx)
	if request.Intention != gfyxOutboundTaskLogPo.Intention {
		// 写入外呼回调日志变更记录表
		record := &outbound_task.GfyxOutboundTaskLogChangeRecordPo{
			TaskLogId:      gfyxOutboundTaskLogPo.Id,
			OldIntention:   gfyxOutboundTaskLogPo.Intention,
			NewIntention:   request.Intention,
			CallInstanceId: request.CallInstanceId,
			CreatedBy:      strconv.FormatInt(userInfo.UserId, 10),
			ModifiedBy:     strconv.FormatInt(userInfo.UserId, 10),
			CreatedOn:      time.Now(),
			ModifiedOn:     time.Now(),
		}
		err = s.outboundTaskRepo.InsertTaskLogChangeRecord(ctx, tx, record)
		if err != nil {
			tx.Rollback()
			return err
		}
	}
	tx.Commit()
	return nil
}

func (s *outboundTaskService) HandleCallback(ctx context.Context, request *pb.CallbackRequest) error {
	//1.保存回调记录
	taskLogPo := &outbound_task.GfyxOutboundTaskLogPo{
		CompanyId:       request.CompanyId,
		TaskId:          request.TaskId,
		CallInstanceId:  request.CallInstanceId,
		CompanyName:     request.CompanyName,
		Mobile:          request.Mobile,
		Duration:        request.Duration,
		ChatRound:       request.ChatRound,
		FinishStatus:    request.FinishStatus,
		Intention:       request.Intention,
		IntentionRemark: request.IntentRemark,
		PhoneLogs:       request.PhoneLogs,
		HangUp:          request.HangUp,
		IsDeleted:       constants.IsDeletedFalse,
		CreatedOn:       time.Now(),
		CreatedBy:       "",
		ModifiedOn:      time.Now(),
		ModifiedBy:      "",
	}

	// 保存录音文件至我们自己的oss
	if request.AuditUrl != "" {
		fileId, err := s.saveAudio(ctx, request.AuditUrl)
		if err != nil {
			stark.Logger.Errorf(ctx, "HandleCallback：下载音频文件失败，文件地址：%s, 错误信息：%s", request.AuditUrl, err.Error())
		}
		taskLogPo.AudioFileId = fileId
	}
	if request.StartTime != "" {
		// 字符串转时间
		st, err := time.ParseInLocation("2006-01-02 15:04:05", request.StartTime, time.Local)
		if err == nil {
			taskLogPo.StartTime = st
		}
	}
	if request.EndTime != "" {
		et, err := time.ParseInLocation("2006-01-02 15:04:05", request.EndTime, time.Local)
		if err == nil {
			taskLogPo.EndTime = et
		}
	}
	taskLogPo, err := s.outboundTaskRepo.SaveTaskLog(ctx, taskLogPo)
	if err != nil {
		return err
	}

	if taskLogPo.Id == 0 {
		stark.Logger.Infof(ctx, "重复的回调数据：%+v", request)
		return nil
	}

	return nil
}

func (s *outboundTaskService) saveAudio(ctx context.Context, auditUrl string) (int64, error) {
	curUser := usermetadata.GetMetaUserInfo(ctx)
	resp, err := http.Get(auditUrl)
	if err != nil {
		return 0, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	contentLength := resp.Header.Get("Content-Length")
	fileSize, _ := strconv.ParseInt(contentLength, 10, 64)
	fileType := resp.Header.Get("Content-Type")

	business := constants.PathOutboundCallVoice

	fileExt := ""
	lastDotIndex := strings.LastIndex(auditUrl, ".")
	if lastDotIndex != -1 {
		fileExt = auditUrl[lastDotIndex:]
	}

	fileName := ""
	lastObliqueLineIndex := strings.LastIndex(auditUrl, "/")
	if lastObliqueLineIndex != -1 && lastObliqueLineIndex != len(auditUrl) {
		fileName = auditUrl[lastObliqueLineIndex+1:]
	}
	// 保存音频文件至oss
	ossFilePath, _, err := ossfile.PutOss(resp.Body, business, curUser.TenantCode, fileExt, 1)
	if err != nil {
		return 0, err
	}
	newFilePo, err := s.fileRepo.SaveFile(ctx, &file.GfyxFilesPo{
		Business:   business,
		FileType:   fileType,
		FileName:   fileName,
		FilePath:   ossFilePath,
		FileSize:   fileSize,
		IsDeleted:  constants.IsDeletedFalse,
		CreatedOn:  time.Now(),
		CreatedBy:  curUser.TenantUserUid,
		ModifiedOn: time.Now(),
		ModifiedBy: curUser.TenantUserUid,
	})
	if err != nil {
		return 0, err
	}
	return newFilePo.Id, nil
}

func (s *outboundTaskService) CreateTask(ctx context.Context, request *pb.CreateTaskRequest) (*pb.CreateTaskResponse, error) {
	// 构建请求参数
	var err error
	params := baiying_remote.CreateTaskRequest{}
	params.CallJobName = request.TaskName
	// 任务类型如果为定时任务，则需要设置定时的启动时间
	params.CallJobType = request.TaskType
	if request.TaskType == constants.OutboundTaskTypeAuto {
		// 定时时间默认为 周一至周的9点-晚上8点
		params.StartDate = request.StartDate
	}
	params.WorkingTimeRuleList, err = s.getTaskCallWorkingTimeRule()
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallWorkTimeError, "时间段格式解析错误")
	}
	// 话术
	params.RobotDefId = request.RobotDefId
	// 线路id 百应的接口文档标注有问题，其实是字符串
	userPhoneIdsStr, err := json.Marshal(request.UserPhoneIds)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallPhoneError, "线路号码错误")
	}
	params.UserPhoneIds = string(userPhoneIdsStr)
	// 坐席数
	params.ConcurrencyQuota = request.ConcurrencyQuota
	// 重播次数 （呼损10，外呼失败2，无法接通3），重拨间隔每5分钟
	if request.RepeatCallCnt > 0 && request.RepeatCallCnt <= 5 {
		params.RepeatCall = true
		params.RepeatCallRule, err = s.getTaskCallRepeatRule(request.RepeatCallCnt)
		if err != nil {
			return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallRepeatRuleError, "重拨规则解析错误")
		}
	}
	// 调用远程数据
	remoteResp, err := s.baiyingRepo.CreateTask(ctx, request.CompanyId, &params)
	if err != nil {
		stark.Logger.Errorf(ctx, "CreateTask：调用百应创建任务接口失败，错误信息：%+v", err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallByApiError, "调用创建任务接口失败")
	}
	if remoteResp.Code != 200 {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallByApiError, remoteResp.ResultMsg)
	}
	ByTaskId := remoteResp.Data.CallJobId

	// 保存当前任务
	taskPo := &outbound_task.GfyxOutboundTaskPo{
		TaskName:   request.TaskName,
		UserId:     request.UserId,
		CompanyId:  request.CompanyId,
		RealTaskId: ByTaskId,
		SeatQuota:  request.ConcurrencyQuota,
		Status:     constants.OutboundTaskStatusUnStart,
		IsDeleted:  constants.IsDeletedFalse,
		CreatedOn:  time.Now(),
		CreatedBy:  fmt.Sprintf("%d", request.UserId),
		ModifiedOn: time.Now(),
		ModifiedBy: fmt.Sprintf("%d", request.UserId),
	}
	taskResult, err := s.outboundTaskRepo.SaveTask(ctx, taskPo)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallWorkSaveTaskError, "保存任务失败")
	}
	pushUserList := make([]*outbound_task.GfyxOutboundTaskPushUserPo, 0)
	// 保存推送用户记录 PushUserIds
	for _, pushUserId := range request.PushUserIds {
		pushUserPo := &outbound_task.GfyxOutboundTaskPushUserPo{
			UserId:     pushUserId,
			TaskId:     taskResult.Id,
			RealTaskId: taskResult.RealTaskId,
			IsDeleted:  constants.IsDeletedFalse,
			CreatedOn:  time.Now(),
			CreatedBy:  fmt.Sprintf("%d", request.UserId),
			ModifiedOn: time.Now(),
			ModifiedBy: fmt.Sprintf("%d", request.UserId),
		}
		pushUserList = append(pushUserList, pushUserPo)
	}
	_, err = s.outboundTaskRepo.SaveTaskPushUser(ctx, taskResult.Id, pushUserList)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallWorkSavePushUserError, "保存任务推送人失败")
	}

	return &pb.CreateTaskResponse{TaskId: taskResult.RealTaskId}, nil
}

// UpdateTask 更新任务
func (s *outboundTaskService) UpdateTask(ctx context.Context, request *pb.UpdateTaskRequest) (*pb.UpdateTaskResponse, error) {
	// 构建请求参数
	var err error
	params := baiying_remote.UpdateTaskRequest{}
	// 是否关闭暂时停止功能 默认 false 不关闭
	params.BreakStartClose = false
	params.CallJobId = request.TaskId
	params.CallJobName = request.TaskName
	// 任务类型如果为定时任务，则需要设置定时的启动时间
	params.CallJobType = request.TaskType
	if request.TaskType == constants.OutboundTaskTypeAuto {
		// 定时时间默认为 周一至周的9点-晚上8点
		params.StartDate = request.StartDate
	}
	params.WorkingTimeRuleList, err = s.getTaskCallWorkingTimeRule()
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallWorkTimeError, "时间段格式解析错误")
	}
	// 话术
	params.RobotDefId = request.RobotDefId
	// 线路id 百应的接口文档标注有问题，其实是字符串
	userPhoneIdsStr, err := json.Marshal(request.UserPhoneIds)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallPhoneError, "线路号码错误")
	}
	params.UserPhoneIds = string(userPhoneIdsStr)
	// 坐席数
	params.ConcurrencyQuota = request.ConcurrencyQuota
	// 重播次数 （呼损10，外呼失败2，无法接通3），重拨间隔每5分钟
	if request.RepeatCallCnt > 0 && request.RepeatCallCnt <= 5 {
		params.RepeatCall = true
		params.RepeatCallRule, err = s.getTaskCallRepeatRule(request.RepeatCallCnt)
		if err != nil {
			return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallRepeatRuleError, "重拨规则解析错误")
		}
	}
	// 调用远程数据
	remoteResp, err := s.baiyingRepo.UpdateTask(ctx, request.CompanyId, &params)
	if err != nil {
		stark.Logger.Errorf(ctx, "CreateTask：调用百应更新任务接口失败，错误信息：%+v", err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallByApiError, "调用更新任务接口失败")
	}
	if remoteResp.Code != 200 {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallByApiError, remoteResp.ResultMsg)
	}
	// 保存任务信息
	taskPo, err := s.outboundTaskRepo.GetTaskByRealTaskId(ctx, request.TaskId)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallGetTaskError, "获取任务记录失败")
	}
	if taskPo.Id == 0 {
		taskPo.RealTaskId = request.TaskId
		taskPo.UserId = request.UserId
		taskPo.CompanyId = request.CompanyId
		taskPo.CreatedBy = fmt.Sprintf("%d", request.UserId)
		taskPo.CreatedOn = time.Now()
	}
	taskPo.TaskName = request.TaskName
	taskPo.SeatQuota = request.ConcurrencyQuota
	taskPo.Status = constants.OutboundTaskStatusUnStart
	taskPo.ModifiedOn = time.Now()
	taskPo.ModifiedBy = fmt.Sprintf("%d", request.UserId)
	taskPo.IsDeleted = constants.IsDeletedFalse
	_, err = s.outboundTaskRepo.SaveTask(ctx, taskPo)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallWorkSaveTaskError, "保存任务失败")
	}

	pushUserList := make([]*outbound_task.GfyxOutboundTaskPushUserPo, 0)
	// 保存推送用户记录 PushUserIds
	for _, pushUserId := range request.PushUserIds {
		pushUserPo := &outbound_task.GfyxOutboundTaskPushUserPo{
			UserId:     pushUserId,
			TaskId:     taskPo.Id,
			RealTaskId: taskPo.RealTaskId,
			IsDeleted:  constants.IsDeletedFalse,
			CreatedOn:  time.Now(),
			CreatedBy:  fmt.Sprintf("%d", request.UserId),
			ModifiedOn: time.Now(),
			ModifiedBy: fmt.Sprintf("%d", request.UserId),
		}
		pushUserList = append(pushUserList, pushUserPo)
	}
	_, err = s.outboundTaskRepo.SaveTaskPushUser(ctx, taskPo.Id, pushUserList)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallWorkSavePushUserError, "保存任务推送人失败")
	}

	return &pb.UpdateTaskResponse{
		TaskId: request.TaskId,
	}, nil
}

func (s *outboundTaskService) DeleteTask(ctx context.Context, request *pb.DeleteTaskRequest) (*pb.DeleteTaskResponse, error) {
	// 调用删除接口
	var err error
	params := baiying_remote.DeleteTaskRequest{}
	params.CallJobId = request.TaskId
	params.CompanyId = request.CompanyId
	remoteResp, err := s.baiyingRepo.DeleteTask(ctx, request.CompanyId, &params)
	if err != nil {
		stark.Logger.Errorf(ctx, "CreateTask：调用百应更新任务接口失败，错误信息：%+v", err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallByApiError, "调用更新任务接口失败")
	}
	if remoteResp.Code != 200 {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallByApiError, remoteResp.ResultMsg)
	}
	// 删除任务信息
	conditions := map[string]interface{}{"real_task_id": request.TaskId, "company_id": request.CompanyId}
	columns := map[string]interface{}{"is_deleted": constants.IsDeletedTrue, "modified_by": request.UserId, "modified_on": time.Now()}
	_, err = s.outboundTaskRepo.UpdateTask(ctx, conditions, columns)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallDelTaskError, "删除任务失败")
	}
	return &pb.DeleteTaskResponse{
		IsSucceed: true,
	}, nil
}

// ExecuteTask 执行任务
func (s *outboundTaskService) ExecuteTask(ctx context.Context, request *pb.ExecuteTaskRequest) (*pb.ExecuteTaskResponse, error) {
	// 初始化参数
	params := baiying_remote.ExecuteTaskRequest{}
	params.CompanyId = request.CompanyId
	params.Command = request.Command
	// 初始化协程参数
	//var wg sync.WaitGroup
	//ch := make(chan string, len(request.TaskIds))
	//errCh := make(chan error, len(request.TaskIds))
	// 如果默认为空，则执行所有
	if len(request.TaskIds) == 0 {
		getListParams := baiying_remote.GetTaskListRequest{}
		getListParams.CompanyId = request.CompanyId
		// 暂停任务只能暂停进行中的
		if request.Command == constants.TaskCommandPause {
			getListParams.CallJobStatus = constants.TaskStatusInProgress
		}
		request.TaskIds, _ = s.GetAllTaskListId(ctx, &getListParams)
	}

	errTaskIds := make([]int64, 0)
	errTaskDetail := make(map[int64]string, 0)
	for _, v := range request.TaskIds {
		params.CallJobId = v
		remoteResp, err := s.baiyingRepo.ExecuteTask(ctx, request.CompanyId, &params)
		if err != nil {
			stark.Logger.Errorf(ctx, "batch call ExecuteTask error, request:%+v, err:%+v", request, err)
			errTaskIds = append(errTaskIds, v)
			errTaskDetail[v] = err.Error()
			continue
		}
		// 操作类型，包括
		// 1开始(只有未开始、手动暂停状态的任务可以开始)
		// 2手动暂停(只有可运行、自动暂停、排队中、进行中、线路欠费、AI到期、短信欠费状态的任务可以手动暂停)
		// 3终止(只有未开始、可运行、进行中、手动暂停、自动暂停、排队中状态的任务可以终止)
		if remoteResp.Code != 200 {
			stark.Logger.Errorf(ctx, "batch call ExecuteTask status error, request:%+v, response:%+v", request, remoteResp)
			errTaskIds = append(errTaskIds, v)
			errTaskDetail[v] = remoteResp.ResultMsg
			continue
		}
	}

	if len(errTaskIds) > 0 {
		if errMsg, ok := errTaskDetail[errTaskIds[0]]; ok {
			return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallExecuteTaskError, "执行失败的任务失败，原因是"+errMsg)
		}
	}

	return &pb.ExecuteTaskResponse{
		IsSucceed: true,
	}, nil
}

// getTaskCallWorkingTimeRule 获取拨号时间段规则
func (s *outboundTaskService) getTaskCallWorkingTimeRule() (string, error) {
	// 定义启动规则
	workingTimeRuleList := make([]baiying_remote.WorkingTimeRule, 0)
	timeRangeList := make([]baiying_remote.TimeRange, 0)
	weekList := []int{1, 2, 3, 4, 5, 6, 7}
	timeRangeList = append(timeRangeList, baiying_remote.TimeRange{
		StartTime: "09:00:00",
		EndTime:   "20:00:00",
	})
	workingTimeRule := baiying_remote.WorkingTimeRule{
		TimeRangeList: timeRangeList,
		Weeks:         weekList,
	}
	workingTimeRuleList = append(workingTimeRuleList, workingTimeRule)
	workingTimeRuleListStr, err := json.Marshal(workingTimeRuleList)
	if err != nil {
		return "", err
	}
	return string(workingTimeRuleListStr), nil
}

// getTaskCallRepeatRule 获取重播规则
func (s *outboundTaskService) getTaskCallRepeatRule(cnt int32) (string, error) {
	phoneStatusList := []int{2, 3, 10}
	rules := make([]baiying_remote.RepeatCallRuleItem, 0)
	for i := 0; i < len(phoneStatusList); i++ {
		rule := baiying_remote.RepeatCallRuleItem{
			Interval:    5,
			PhoneStatus: phoneStatusList[i],
			Times:       cnt,
		}
		rules = append(rules, rule)
	}
	rulesStr, err := json.Marshal(rules)
	if err != nil {
		return "", err
	}
	return string(rulesStr), nil
}

func buildClueCondition(request *pb.GetOutboundClueListRequest) map[string]interface{} {
	condition := map[string]interface{}{"is_deleted": constants.IsDeletedFalse}

	if request.Keyword != "" {
		condition["keyword"] = request.Keyword
	}
	if request.TaskId != 0 {
		condition["task_id"] = request.TaskId
	}
	if len(request.Intention) != 0 {
		condition["intention"] = request.Intention
	}
	if len(request.FinishStatus) != 0 {
		condition["finish_status"] = request.FinishStatus
	}
	if request.StartDate != "" {
		condition["start_date"] = request.StartDate
	}
	if request.EndDate != "" {
		condition["end_date"] = request.EndDate
	}
	if request.DurationMin != 0 {
		condition["duration_min"] = request.DurationMin
	}
	if request.DurationMax != 0 {
		condition["duration_max"] = request.DurationMax
	}
	if request.ChatRoundMin != 0 {
		condition["chat_round_min"] = request.ChatRoundMin
	}
	if request.ChatRoundMax != 0 {
		condition["chat_round_max"] = request.ChatRoundMax
	}
	if request.AuditStatus != 0 {
		condition["audit_status"] = request.AuditStatus
	}
	return condition
}

func (s *outboundTaskService) GetClueList(ctx context.Context, request *pb.GetOutboundClueListRequest) (*pb.GetOutboundClueListResponse, error) {
	count, list, err := s.outboundTaskRepo.QueryTaskLogList(ctx, buildClueCondition(request), &utils.PageInfo{
		PageSize: int(request.PageSize),
		Page:     int(request.Page),
		Total:    0,
	})
	if err != nil {
		return nil, err
	}
	resp := &pb.GetOutboundClueListResponse{
		Total: int32(count),
		List:  []*pb.OutboundClueItem{},
	}

	if len(list) == 0 {
		return resp, nil
	}

	var taskIds []int64
	taskIdSet := make(map[int64]bool)
	for _, item := range list {
		if _, ok := taskIdSet[item.TaskId]; !ok {
			taskIds = append(taskIds, item.TaskId)
			taskIdSet[item.TaskId] = true
		}
	}

	taskList, err := s.outboundTaskRepo.GetTaskByTaskIds(ctx, taskIds)
	if err != nil {
		return nil, err
	}
	taskMap := make(map[int64]*outbound_task.GfyxOutboundTaskPo)
	for _, task := range taskList {
		taskMap[task.RealTaskId] = task
	}

	for _, clueItem := range list {
		rccProjectName := ""
		rccProjectPo, _ := s.outboundRccProjectRepo.QueryOne(ctx, clueItem.TaskId, clueItem.Mobile, clueItem.CompanyName)
		if rccProjectPo != nil && rccProjectPo.RccProjectName != "" {
			rccProjectName = rccProjectPo.RccProjectName
		}
		taskInfo := taskMap[clueItem.TaskId]
		taskName := ""
		if taskInfo != nil {
			taskName = taskInfo.TaskName
		}
		resp.List = append(resp.List, &pb.OutboundClueItem{
			Id:             clueItem.Id,
			RccProjectName: rccProjectName,
			CompanyName:    clueItem.CompanyName,
			CallInstanceId: clueItem.CallInstanceId,
			Mobile:         clueItem.Mobile,
			Duration:       clueItem.Duration,
			ChatRound:      clueItem.ChatRound,
			FinishStatus:   clueItem.FinishStatus,
			Intention:      clueItem.Intention,
			StartTime:      clueItem.StartTime.Format("2006-01-02 15:04:05"),
			EndTime:        clueItem.EndTime.Format("2006-01-02 15:04:05"),
			TaskId:         clueItem.TaskId,
			IsRead:         clueItem.IsRead,
			AuditStatus:    clueItem.AuditStatus,
			TaskName:       taskName,
		})
	}

	return resp, nil
}

func (s *outboundTaskService) sendMessage(ctx context.Context, mobile, customerName string, callInstanceId int64, startTime string, userIds []int64) error {
	templateId := "gfyx-outbound-intention-ab-notice"
	userList, _, err := s.userRepo.UserList(ctx, userIds, 1, "", &utils.PageInfo{
		PageSize: len(userIds),
		Page:     1,
		Total:    0,
	})
	if err != nil {
		return err
	}
	if len(userList) == 0 {
		return nil
	}
	msgClient := msg_center.NewClient(nil)
	for _, userItem := range userList {
		userBindPo, _ := s.userBindRepo.GetUserBind(ctx, userItem.Uid)
		if userBindPo != nil && userBindPo.OpenId != "" {
			content := map[string]interface{}{
				"username":         userItem.Name,
				"customer_name":    customerName,
				"mobile":           mobile,
				"call_time":        startTime,
				"call_instance_id": callInstanceId,
			}
			contentStr, _ := json.Marshal(content)
			msgStd := &msg_center.MsgBody{
				PushOrderCode: templateId,
				Wx: &msg_center.MsgContent{
					ReceiverIds: []string{userBindPo.OpenId},
					Content:     string(contentStr),
					TryTimes:    0,
					ConfirmUrl:  "",
					CallbackUrl: "",
				},
			}
			if err := msgClient.Push(ctx, msgStd); err != nil {
				stark.Logger.Errorf(ctx, "供方营销-AI外呼高意向通知发送失败:%s", err.Error())
			}
		}
	}

	return nil
}

func (s *outboundTaskService) GetClueDetail(ctx context.Context, request *pb.GetOutboundClueDetailRequest) (*pb.GetOutboundClueDetailResponse, error) {
	condition := map[string]interface{}{"is_deleted": constants.IsDeletedFalse}

	if request.CallInstanceId != 0 {
		condition["call_instance_id"] = request.CallInstanceId
	} else if request.Mobile != "" && request.TaskId != 0 {
		condition["mobile"] = request.Mobile
		condition["task_id"] = request.TaskId
	} else {
		return nil, errors.New("参数错误")
	}
	resp := &pb.GetOutboundClueDetailResponse{}
	_, taskLogList, err := s.outboundTaskRepo.QueryTaskLogList(ctx, condition, nil)
	if err != nil {
		return nil, err
	}

	for _, taskLogPo := range taskLogList {
		taskLogPb := &pb.TaskLog{
			CompanyName: taskLogPo.CompanyName,
			Mobile:      taskLogPo.Mobile,
			Intention:   taskLogPo.Intention,
			Duration:    taskLogPo.Duration,
			ChatRound:   taskLogPo.ChatRound,
			HangUp:      taskLogPo.HangUp,
			StartTime:   taskLogPo.StartTime.Format("2006-01-02 15:04:05"),
			AuditRemark: taskLogPo.AuditRemark,
		}

		var phoneLogs []*outbound_task.PhoneLogVo
		_ = json.Unmarshal([]byte(taskLogPo.PhoneLogs), &phoneLogs)
		for _, phoneLog := range phoneLogs {
			taskLogPb.PhoneLogs = append(taskLogPb.PhoneLogs, &pb.PhoneLog{
				Content:   phoneLog.Content,
				StartTime: phoneLog.StartTime,
				EndTime:   phoneLog.EndTime,
				Speaker:   phoneLog.Speaker,
			})
		}

		fileInfo, _ := s.fileRepo.QueryById(ctx, taskLogPo.AudioFileId)
		if fileInfo != nil {
			taskLogPb.AuditUrl, _ = ossfile.GetSignURL(fileInfo.FilePath)
		}
		resp.List = append(resp.List, taskLogPb)
	}
	return resp, nil
}

func (s *outboundTaskService) ExportTaskLog(ctx context.Context, request *pb.ExportTaskLogRequest) (*pb.ExportTaskLogResponse, error) {
	if request == nil {
		return nil, errors.New("ExportTaskLog 参数错误")
	}

	condition := map[string]interface{}{"is_deleted": constants.IsDeletedFalse}
	pageInfo := &utils.PageInfo{Page: 1, PageSize: 10000}
	if len(request.Ids) > 0 {
		//用户选中导出指定id的记录
		condition["ids"] = request.Ids
	} else {
		//用户导出指定条件的记录
		condition = buildClueCondition(&pb.GetOutboundClueListRequest{
			Keyword:      request.Keyword,
			TaskId:       request.TaskId,
			Intention:    request.Intention,
			DurationMin:  request.DurationMin,
			DurationMax:  request.DurationMax,
			ChatRoundMin: request.ChatRoundMin,
			ChatRoundMax: request.ChatRoundMax,
			FinishStatus: request.FinishStatus,
			StartDate:    request.StartDate,
			EndDate:      request.EndDate,
		})
	}
	_, taskLogList, err := s.outboundTaskRepo.QueryTaskLogList(ctx, condition, pageInfo)
	if err != nil {
		return nil, err
	}
	resp := &pb.ExportTaskLogResponse{}
	// 写excel文件
	fileUrl, err := s.writeExcelAndUploadToOss(ctx, taskLogList)
	if err != nil {
		return nil, err
	}
	resp.FileUrl = fileUrl
	return resp, nil
}

func (s *outboundTaskService) writeExcelAndUploadToOss(ctx context.Context, taskLogList []*outbound_task.GfyxOutboundTaskLogPo) (string, error) {
	curUserInfo := usermetadata.GetMetaUserInfo(ctx)

	f := excelize.NewFile()
	defer func() {
		if err := f.Close(); err != nil {
			stark.Logger.Errorf(ctx, "excelize file close err:%s", err.Error())
		}
	}()

	defaultSheet := "Sheet1"
	sw, err := f.NewStreamWriter(defaultSheet)
	if err != nil {
		stark.Logger.Errorf(ctx, "excelize NewStreamWriter err:%s", err.Error())
		return "", err
	}
	// 设置列宽
	err = sw.SetColWidth(1, 3, 15)
	err = sw.SetColWidth(7, 9, 19)

	//styleID, _ := f.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "000000"}})
	//初始化首行
	err = sw.SetRow("A1", []interface{}{
		excelize.Cell{Value: "客户名称"},
		excelize.Cell{Value: "项目名称"},
		excelize.Cell{Value: "通话记录ID"},
		excelize.Cell{Value: "联系方式"},
		excelize.Cell{Value: "通话时长"},
		excelize.Cell{Value: "通话轮次"},
		excelize.Cell{Value: "通话状态"},
		excelize.Cell{Value: "客户意向"},
		excelize.Cell{Value: "开始时间"},
		excelize.Cell{Value: "结束时间"},
		excelize.Cell{Value: "通话记录"},
	})
	if err != nil {
		stark.Logger.Errorf(ctx, "excelize SetRow err:%s", err.Error())
		return "", err
	}

	for i, taskLogPo := range taskLogList {
		rccProjectName := ""
		rccProjectPo, _ := s.outboundRccProjectRepo.QueryOne(ctx, taskLogPo.TaskId, taskLogPo.Mobile, taskLogPo.CompanyName)
		if rccProjectPo != nil && rccProjectPo.RccProjectName != "" {
			rccProjectName = rccProjectPo.RccProjectName
		}
		phoneLogsJsonText, _ := s.formatPhoneLogsJsonToText(ctx, taskLogPo.PhoneLogs)
		err = sw.SetRow(fmt.Sprintf("A%d", i+2), []interface{}{
			excelize.Cell{Value: taskLogPo.CompanyName},
			excelize.Cell{Value: rccProjectName},
			excelize.Cell{Value: fmt.Sprintf("%d", taskLogPo.CallInstanceId)},
			excelize.Cell{Value: taskLogPo.Mobile},
			excelize.Cell{Value: fmt.Sprintf("%d秒", taskLogPo.Duration)},
			excelize.Cell{Value: fmt.Sprintf("%d次", taskLogPo.ChatRound)},
			excelize.Cell{Value: constants.GetFinishStatusRemark(taskLogPo.FinishStatus)},
			excelize.Cell{Value: constants.GetIntentionRemark(taskLogPo.Intention)},
			excelize.Cell{Value: taskLogPo.StartTime.Format("2006-01-02 15:04:05")},
			excelize.Cell{Value: taskLogPo.EndTime.Format("2006-01-02 15:04:05")},
			excelize.Cell{Value: phoneLogsJsonText},
		})
		if err != nil {
			stark.Logger.Errorf(ctx, "excelize SetRow err:%s", err.Error())
			return "", err
		}
	}
	err = sw.Flush()
	if err != nil {
		stark.Logger.Errorf(ctx, "excelize Flush err:%s", err.Error())
		return "", err
	}

	//err = f.SaveAs("./outbound_task_log.xlsx")
	//if err != nil {
	//	stark.Logger.Errorf(ctx, "excelize SaveAs err:%s", err.Error())
	//	return err
	//}

	excelFileBuffer, err := f.WriteToBuffer()
	if err != nil {
		stark.Logger.Errorf(ctx, "excelize WriteToBuffer err:%s", err.Error())
		return "", err
	}
	_, fileUrl, err := ossfile.PutOss(excelFileBuffer, constants.PathOutboundContactExport, curUserInfo.TenantCode, ".xlsx", 1)
	if err != nil {
		stark.Logger.Errorf(ctx, "excelize ossfile.PutOss err:%s", err.Error())
		return "", err
	}
	return fileUrl, nil
}

func (s *outboundTaskService) GetAllTaskListId(ctx context.Context, request *baiying_remote.GetTaskListRequest) ([]int64, error) {
	var taskIds []int64
	// 循环调用
	pageNum := 1
	pageSize := 50
	maxPage := 1000
	for {
		request.PageNum = int32(pageNum)
		request.PageSize = int32(pageSize)
		// 调用百应接口
		remoteResp, err := s.baiyingRepo.GetTaskList(ctx, request.CompanyId, request)
		if err != nil {
			stark.Logger.Errorf(ctx, "GetAllTaskListId error, request:%v, err:%v", request, err)
			return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallExecuteTaskError, err.Error())
		}
		if remoteResp.Code != 200 {
			// stark.Logger.Errorf(ctx, "GetAllTaskListId return failed data:%v", remoteResp)
			return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallExecuteTaskError, remoteResp.ResultMsg)
		}

		// 如果当前没有数据，则跳出循环
		if len(remoteResp.Data.List) <= 0 {
			break
		}

		for _, task := range remoteResp.Data.List {
			taskIds = append(taskIds, task.CallJobId)
		}
		pageNum++
		// 防止死循环
		if pageNum > maxPage {
			break
		}
	}

	return taskIds, nil
}

func (s *outboundTaskService) GetTaskList(ctx context.Context, request *pb.GetTaskListRequest) (*pb.GetTaskListResponse, error) {
	params := baiying_remote.GetTaskListRequest{}
	params.CompanyId = request.CompanyId
	params.PageNum = request.Page
	params.PageSize = request.PageSize

	// 构建时间查询条件
	if request.CreateDate != "" && request.EndDate != "" {
		params.CreateDate = request.CreateDate
		params.EndDate = request.EndDate
	}

	// 构建查询任务
	if request.TaskName != "" {
		params.JobName = request.TaskName
	}

	// 调用百应接口
	remoteResp, err := s.baiyingRepo.GetTaskList(ctx, request.CompanyId, &params)
	if err != nil {
		stark.Logger.Errorf(ctx, "GetTaskList error, request:%v, err:%v", params, err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallExecuteTaskError, err.Error())
	}
	if remoteResp.Code != 200 {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskCallExecuteTaskError, remoteResp.ResultMsg)
	}

	response := &pb.GetTaskListResponse{
		Total: remoteResp.Data.Total,
		List:  make([]*pb.TaskItem, 0),
	}

	// 调用本地获取创建时间和创建人
	if len(remoteResp.Data.List) > 0 {
		realTaskIds := make([]int64, 0)
		tempMap := make(map[int64]*outbound_task.GfyxOutboundTaskPo, 0)
		for _, task := range remoteResp.Data.List {
			realTaskIds = append(realTaskIds, task.CallJobId)
		}
		_, list, err := s.outboundTaskRepo.GetTaskList(ctx, map[string]interface{}{"t.real_task_ids": realTaskIds}, nil)
		if err != nil {
			return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetLocalListError, err.Error())
		}
		for _, v := range list {
			tempMap[v.RealTaskId] = v
		}

		for _, v := range remoteResp.Data.List {
			pbItem := &pb.TaskItem{
				TaskId:     v.CallJobId,
				TaskName:   v.JobName,
				TaskStatus: v.CallJobStatus,
				SeatNum:    0,
				TotalCount: v.TotalCount,
				DoneCount:  v.DoneCount,
			}
			if existTask, ok := tempMap[v.CallJobId]; ok {
				pbItem.SeatNum = existTask.SeatQuota
				pbItem.CreatedOn = existTask.CreatedOn.Format("2006-01-02 15:04:05")
				pbItem.CreatedBy = existTask.CreatedBy
			}
			response.List = append(response.List, pbItem)
		}
	}

	return response, nil
}

func (s *outboundTaskService) GetTaskDetail(ctx context.Context, request *pb.GetTaskDetailRequest) (*pb.GetTaskDetailResponse, error) {
	// 构建参数
	params := baiying_remote.GetTaskDetailRequest{}
	params.CompanyId = request.CompanyId
	params.CallJobId = request.TaskId

	// 调用百应接口
	remoteResp, err := s.baiyingRepo.GetTaskDetail(ctx, request.CompanyId, &params)
	if err != nil {
		stark.Logger.Errorf(ctx, "GetTaskDetail error, request:%v, err:%v", params, err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetDetailError, err.Error())
	}
	if remoteResp.Code != 200 {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetDetailError, remoteResp.ResultMsg)
	}
	// 转换毫秒时间戳
	var startDate string
	if remoteResp.Data.StartDate > 0 {
		startDate = time.Unix(remoteResp.Data.StartDate/1000, 0).Format("2006-01-02")
	}
	response := &pb.GetTaskDetailResponse{
		TaskId:             remoteResp.Data.CallJobId,
		TaskName:           remoteResp.Data.JobName,
		TaskType:           remoteResp.Data.JobType,
		TaskStatus:         remoteResp.Data.CallJobStatus,
		RobotDefName:       remoteResp.Data.RobotDefName,
		SeatNum:            remoteResp.Data.SeatNum,
		TotalCount:         remoteResp.Data.TotalCount,
		CalledCount:        remoteResp.Data.CalledCount,
		UnavailableCount:   remoteResp.Data.UnavailableCount,
		IntentionCount:     0,
		CallCompletionRate: "0%",
		IntentionRate:      "0%",
		StartDate:          startDate,
		RobotDefId:         remoteResp.Data.RobotDefId,
		UserPhoneIds:       remoteResp.Data.UserPhoneIds,
		ConcurrencyQuota:   remoteResp.Data.SeatNum,
	}

	// 处理重播次数
	if remoteResp.Data.RepeatCall || remoteResp.Data.RepeatCallRule != "" {
		var repeatRules []baiying_remote.RepeatCallRuleItem
		err = json.Unmarshal([]byte(remoteResp.Data.RepeatCallRule), &repeatRules)
		if err != nil {
			stark.Logger.Errorf(ctx, "json Unmarshal error, params:%v, err:%v", remoteResp.Data.RepeatCallRule, err)
			response.RepeatCallCnt = 0
		}
		// 取其中一个即可
		for _, v := range repeatRules {
			response.RepeatCallCnt = v.Times
			break
		}
	} else {
		response.RepeatCallCnt = 0
	}

	// 计算主叫号码
	callPhoneStr := ""
	for _, v := range remoteResp.Data.CallPhones {
		callPhoneStr += v.Phone + ","
	}
	callPhoneStr = strings.TrimRight(callPhoneStr, ",")
	response.CallPhones = callPhoneStr
	// 获取 A, B 用户意向
	conditions := map[string]interface{}{
		"task_id":    request.TaskId,
		"intention":  []string{constants.IntentionA, constants.IntentionB},
		"is_deleted": constants.IsDeletedFalse,
	}
	count, _, err := s.outboundTaskRepo.QueryTaskLogList(ctx, conditions, &utils.PageInfo{
		PageSize: 1,
		Page:     1,
		Total:    0,
	})
	if err != nil {
		stark.Logger.Errorf(ctx, "QueryTaskLogList error, request:%v, err:%v", conditions, err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetDetailError, err.Error())
	}
	response.IntentionCount = int32(count)
	// 计算比率
	// 接通率 = 已完成总数 / 呼叫总数 * 100
	if response.TotalCount != 0 && response.CalledCount != 0 {
		response.CallCompletionRate = fmt.Sprintf("%.2f%%", (float64(response.CalledCount)/float64(response.TotalCount))*100)
	}
	// AB 意向率 = AB 意向总数 / 已完成呼叫总数 * 100
	if response.CalledCount != 0 && response.IntentionCount != 0 {
		response.IntentionRate = fmt.Sprintf("%.2f%%", (float64(response.IntentionCount)/float64(response.CalledCount))*100)
	}
	// 调用推送用户数
	pushUsers, err := s.outboundTaskRepo.GetTaskPushUserByRealTaskId(ctx, request.TaskId)
	if err != nil {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetDetailError, "获取推送用户失败")
	}
	response.PushUserIds = make([]int64, 0)
	for _, v := range pushUsers {
		response.PushUserIds = append(response.PushUserIds, v.UserId)
	}

	return response, nil
}

func (s *outboundTaskService) GetUnStartCallList(ctx context.Context, request *pb.GetUnStartCallListRequest) (*pb.GetUnStartCallListResponse, error) {
	// 构建参数
	params := baiying_remote.GetUnStartCallListRequest{}
	params.CompanyId = request.CompanyId
	params.CallJobId = request.TaskId
	params.PageNum = request.Page
	params.PageSize = request.PageSize

	// 调用百应接口
	remoteResp, err := s.baiyingRepo.GetUnStartCallList(ctx, request.CompanyId, &params)
	if err != nil {
		stark.Logger.Errorf(ctx, "GetUnStartCallList error, request:%v, err:%v", params, err)
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetDetailUnStartListError, err.Error())
	}
	if remoteResp.Code != 200 {
		return nil, ykerrcode.TogRPCError(errcode.OutboundTaskGetDetailUnStartListError, remoteResp.ResultMsg)
	}

	response := &pb.GetUnStartCallListResponse{
		Total: remoteResp.Data.Total,
		List:  make([]*pb.UnStartCustomer, 0),
	}
	for _, v := range remoteResp.Data.List {
		response.List = append(response.List, &pb.UnStartCustomer{
			CallInstanceId:    v.CallInstanceId,
			CustomerName:      v.CustomerName,
			CustomerTelephone: v.CustomerTelephone,
		})
	}

	return response, nil
}

func (s *outboundTaskService) AuditOutboundClue(ctx context.Context, request *pb.AuditOutboundClueRequest) error {
	_, taskLogList, err := s.outboundTaskRepo.QueryTaskLogList(ctx, map[string]interface{}{"id": request.Id, "is_deleted": constants.IsDeletedFalse}, nil)
	if err != nil {
		return err
	}
	if len(taskLogList) == 0 {
		return errors.New("审核记录不存在")
	}
	taskLogPo := taskLogList[0]

	// 修改意向
	err = s.UpdateIntention(ctx, &pb.UpdateIntentionRequest{
		CallInstanceId: taskLogPo.CallInstanceId,
		Intention:      request.Intention,
	})
	if err != nil {
		return err
	}

	//修改审核状态，记录审核备注和审核人
	_, err = s.outboundTaskRepo.UpdateTaskLog(ctx, (&tenant_db.TenantDB{}).GetTenantDB(ctx),
		map[string]interface{}{"id": request.Id},
		map[string]interface{}{"audit_status": 2, "audit_remark": request.AuditRemark, "audit_user": request.AuditUserName, "audit_time": time.Now()})
	if err != nil {
		return err
	}

	//A\B类意向，转为线索
	if request.Intention == constants.IntentionA ||
		request.Intention == constants.IntentionB {
		err = s.transferToClue(ctx, taskLogPo)
		if err != nil {
			return err
		}
	}

	return nil
}

func (s *outboundTaskService) transferToClue(ctx context.Context, taskLogPo *outbound_task.GfyxOutboundTaskLogPo) error {
	arr := strings.Split(taskLogPo.CompanyName, "-")
	var companyName, contactName string
	if len(arr) == 2 {
		companyName = arr[0]
		contactName = arr[1]
	} else {
		contactName = taskLogPo.CompanyName
	}

	var inChargeUserId int64
	userList, _ := s.outboundTaskRepo.GetTaskPushUserByRealTaskId(ctx, taskLogPo.TaskId)
	if len(userList) > 0 {
		inChargeUserId = userList[0].UserId
	}

	// A,B类意向，添加到线索
	clueId, err := s.clueService.ClueSave(ctx, &cluePb.SaveClueRequest{
		ContactName:     contactName,
		ContactMobile:   taskLogPo.Mobile,
		CompanyName:     companyName,
		Source:          constants.ClueSourceOutbound,
		SourceId:        fmt.Sprintf("%d", taskLogPo.Id),
		Status:          constants.ClueStatusUnprocessed,
		InChargeUserId:  inChargeUserId,
		ContactPosition: "",
	})
	if err != nil {
		return err
	}

	clueContactResp, err := s.clueService.GetClueContactList(ctx, &cluePb.GetClueContactListRequest{ClueId: clueId, Page: 1, PageSize: 100})
	if err != nil {
		return err
	}
	contactId := int64(0)
	if clueContactResp != nil && clueContactResp.List != nil && len(clueContactResp.List) > 0 {
		for _, clueContactPo := range clueContactResp.List {
			if clueContactPo.Mobile == taskLogPo.Mobile {
				contactId = clueContactPo.Id
			}
		}
	}

	// 检查跟进记录是否存在，如果已经存在，则不新增跟进记录
	resp, err := s.clueService.GetClueFollowRecordList(ctx, &cluePb.GetClueFollowRecordListRequest{ClueId: clueId, Page: 1, PageSize: 1})
	if err != nil {
		return err
	}
	if resp != nil && resp.Total > 0 {
		// 已有跟进记录，不新建跟进记录
		return nil
	}
	var phoneLogs []*outbound_task.PhoneLogVo
	_ = json.Unmarshal([]byte(taskLogPo.PhoneLogs), &phoneLogs)

	var talkContents []string
	for _, v := range phoneLogs {
		if v.Speaker == "AI" {
			talkContents = append(talkContents, fmt.Sprintf("系统AI：%s", v.Content))
		} else {
			talkContents = append(talkContents, fmt.Sprintf("%s：%s", contactName, v.Content))
		}
	}
	talkContentBytes, _ := json.Marshal(talkContents)
	// 添加线索跟进记录
	_, err = s.clueService.AddClueFollowRecord(ctx, &cluePb.AddClueFollowRecordRequest{
		ClueId:      clueId,
		FollowType:  constants.ClueFollowTypeOutbound,
		ClueStatus:  constants.ClueStatusUnprocessed,
		ContactId:   contactId,
		Content:     fmt.Sprintf("AI外呼高意向客户，对话%d轮。", taskLogPo.ChatRound),
		FromSource:  constants.ClueFollowFromSourceAi,
		TalkContent: string(talkContentBytes),
	})
	if err != nil {
		return err
	}

	// 发送站内信
	var userIds []int64
	for _, pushUserPo := range userList {
		userIds = append(userIds, pushUserPo.UserId)
	}
	userInfoList, _, err := s.userRepo.UserList(ctx, userIds, 1, "", &utils.PageInfo{
		PageSize: len(userIds),
		Page:     1,
		Total:    0,
	})
	if err != nil {
		return err
	}
	if len(userList) == 0 {
		return nil
	}

	for _, userPo := range userInfoList {
		//https://toker.b2btst.com/radar/clue/detail?id=114
		domain := stark.Configer.(stark.ApolloConfigerIface).GetStringValue("gfyx_front_domain", "https://toker.mycaigou.com")
		siteMsgReq := &basePb.SiteMessageRequest{
			MessageType: basePb.MySiteMessageType_CLUE_SUBSCRIBE,
			Title:       "AI外呼高意向线索分配",
			Content:     fmt.Sprintf("尊敬的【%s】，【%s】【%s】已被AI外呼机器人评估为高意向，请及时跟进客户，查看外呼详情", userPo.Name, taskLogPo.CompanyName, taskLogPo.Mobile),
			SenderId:    0,
			ReceiverId:  userPo.Id,
			Link:        fmt.Sprintf("%s/crm/clue/detail?id=%d", domain, clueId),
			TenantCode:  "",
		}
		if err = s.msgService.PushSiteMsg(ctx, siteMsgReq); err != nil {
			stark.Logger.Errorf(ctx, "【AI外呼高意向线索分配】消息推送[站内信]失败:%s", err.Error())
		}
	}
	return nil
}

func (s *outboundTaskService) formatPhoneLogsJsonToText(ctx context.Context, phoneLogs string) (string, error) {
	//[{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"AI","startTime":13,"userMean":"","endTime":0,"content":"喂，您好（停顿1秒），老板，您好！我是中国联塑集团领尚橱柜事业部的招商经理，我看到咱们这边也在做工程，现在正在全国寻找有实力的合作商，您看是否有合作的机会呢？","decisionName":"普通话术","answerStatus":0},{"callInstanceId":256737600141,"aiUnknown":1,"speaker":"ME","startTime":12240,"userMean":"~","endTime":13340,"content":"现在。","decisionName":"普通话术","answerStatus":0},{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"AI","startTime":13486,"userMean":"","endTime":0,"content":"我们联塑领尚成立于2007年。主要以橱柜、收纳柜为主，国内一线品牌，是上市企业中国联塑集团全资控股子公司，同档次品牌中我们操作更灵活便捷，性价比最高。要不这样吧，稍后我加一下您的微信，给您发点资料，您详细了解一下好吧？","decisionName":"普通话术","answerStatus":0},{"callInstanceId":256737600141,"aiUnknown":1,"speaker":"ME","startTime":16840,"userMean":"~","endTime":19520,"content":"你能说慢一点吗？感觉好快啊。","decisionName":"普通话术","answerStatus":2},{"callInstanceId":256737600141,"aiUnknown":1,"speaker":"ME","startTime":19520,"userMean":"~","endTime":22540,"content":"他感觉他要一分钟把话说清楚啊。","decisionName":"普通话术","answerStatus":2},{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"ME","startTime":22540,"userMean":"OK","endTime":24400,"content":"我省话费是吧。","decisionName":"普通话术","answerStatus":2},{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"ME","startTime":29000,"userMean":"没信号","endTime":31720,"content":"对啊，就我都听不清楚他说啥。","decisionName":"普通话术","answerStatus":2},{"callInstanceId":256737600141,"aiUnknown":1,"speaker":"ME","startTime":32460,"userMean":"~","endTime":34420,"content":"跟周杰伦说唱呀。","decisionName":"普通话术","answerStatus":0},{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"AI","startTime":34576,"userMean":"","endTime":0,"content":"好的，那请问这个手机号是您的微信号吗？","decisionName":"普通节点","answerStatus":0},{"callInstanceId":256737600141,"aiUnknown":1,"speaker":"ME","startTime":34680,"userMean":"~","endTime":35680,"content":"这会。","decisionName":"普通节点","answerStatus":2},{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"ME","startTime":39320,"userMean":"OK","endTime":40440,"content":"嗯。","decisionName":"普通节点","answerStatus":0},{"callInstanceId":256737600141,"aiUnknown":0,"speaker":"AI","startTime":40572,"userMean":"OK","endTime":0,"content":"哎好，那我加下您微信，您记得通过一下哈，我们微信沟通。这边就先不打扰您了，再见。","decisionName":"挂机","answerStatus":0}]
	if phoneLogs == "" {
		return "", nil
	}
	outboundTaskPhoneLogs := market.OutboundTaskPhoneLogs{}
	err := json.Unmarshal([]byte(phoneLogs), &outboundTaskPhoneLogs)
	if err != nil {
		stark.Logger.Errorf(ctx, "FormatPhoneLogsJsonToText: %+v, error: %+v", phoneLogs, err)
		return "", err
	}
	phoneLogsJsonText := ""
	for _, v := range outboundTaskPhoneLogs {
		speaker := ""
		if v.Speaker == "AI" {
			speaker = "AI机器人"
		} else {
			speaker = "客户"
		}

		phoneLogsJsonText = phoneLogsJsonText + fmt.Sprintf("%s:%s\n", speaker, v.Content)
	}

	return phoneLogsJsonText, nil
}
